-module(dummy_via).
-export([reset/0,
     register_name/2,
     whereis_name/1,
     unregister_name/1,
     send/2]).


reset() ->
    P = whereis(?MODULE),
    catch unlink(P),
    Ref = erlang:monitor(process, P),
    catch exit(P, kill),
    receive {'DOWN',Ref,_,_,_} -> ok end,
    Me = self(),
    Pid = spawn_link(fun() ->
                 register(?MODULE, self()),
                 Me ! {self(), started},
                 loop([])
             end),
    receive
    {Pid, started} ->
        Pid
    after 10000 ->
        exit(timeout)
    end.

register_name(Name, Pid) when is_pid(Pid) ->
    call({register_name, Name, Pid}).

unregister_name(Name) ->
    call({unregister_name, Name}).

whereis_name(Name) ->
    call({whereis_name, Name}).

send(Name, Msg) ->
    case whereis_name(Name) of
    undefined ->
        exit({badarg, {Name, Msg}});
    Pid when is_pid(Pid) ->
        Pid ! Msg,
        Pid
    end.

call(Req) ->
    MRef = erlang:monitor(process, ?MODULE),
    ?MODULE ! {self(), MRef, Req},
    receive
    {'DOWN', MRef, _, _, _} ->
        erlang:error(badarg);
    {MRef, badarg} ->
        erlang:demonitor(MRef),
        erlang:error(badarg);
    {MRef, Reply} ->
        erlang:demonitor(MRef),
        Reply
    after 5000 ->
        erlang:error(timeout)
    end.

loop(Reg) ->
    receive
    {'DOWN', _, _, P, _} when is_pid(P) ->
        loop([X || {_,Pid,_} = X <- Reg, Pid =/= P]);
    {From, Ref, Request} when is_pid(From), is_reference(Ref) ->
        {Reply, NewReg} = handle_request(Request, Reg),
        From ! {Ref, Reply},
        loop(NewReg)
    end.

handle_request({register_name, Name, Pid}, Reg) when is_pid(Pid) ->
    case lists:keyfind(Name, 1, Reg) of
    false ->
        Ref = erlang:monitor(process, Pid),
        {yes, [{Name, Pid, Ref}|Reg]};
    _ ->
        {no, Reg}
    end;
handle_request({whereis_name, Name}, Reg) ->
    case lists:keyfind(Name, 1, Reg) of
    {_, Pid, _} ->
        {Pid, Reg};
    false ->
        {undefined, Reg}
    end;
handle_request({unregister_name, Name}, Reg) ->
    case lists:keyfind(Name, 1, Reg) of
    {_, _, Ref} ->
        catch erlang:demonitor(Ref);
    _ ->
        ok
    end,
    {ok, lists:keydelete(Name, 1, Reg)};
handle_request(_, Reg) ->
    {badarg, Reg}.